import Foundation
import UIKit
import Display
import AsyncDisplayKit
import Postbox
import TelegramCore
import SwiftSignalKit
import TelegramPresentationData
import TelegramUIPreferences
import AccountContext
import StickerResources
import PhotoResources
import TelegramStringFormatting
import AnimatedCountLabelNode
import AnimatedNavigationStripeNode
import ContextUI
import RadialStatusNode
import InvisibleInkDustNode
import TextFormat
import ChatPresentationInterfaceState
import TextNodeWithEntities
import AnimationCache
import MultiAnimationRenderer
import TranslateUI
import ChatControllerInteraction

private enum PinnedMessageAnimation {
    case slideToTop
    case slideToBottom
}

private final class ButtonsContainerNode: ASDisplayNode {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if let subnodes = self.subnodes {
            for subnode in subnodes {
                if let result = subnode.view.hitTest(self.view.convert(point, to: subnode.view), with: event) {
                    return result
                }
            }
        }
        return nil
    }
}

final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode {
    private let context: AccountContext
    private let tapButton: HighlightTrackingButtonNode
    private let buttonsContainer: ButtonsContainerNode
    private let closeButton: HighlightableButtonNode
    private let listButton: HighlightableButtonNode
    private let activityIndicatorContainer: ASDisplayNode
    private let activityIndicator: RadialStatusNode
    
    private let contextContainer: ContextControllerSourceNode
    private let clippingContainer: ASDisplayNode
    private let contentContainer: ASDisplayNode
    private let contentTextContainer: ASDisplayNode
    private let lineNode: AnimatedNavigationStripeNode
    private let titleNode: AnimatedCountLabelNode
    private let textNode: TextNodeWithEntities
    private var spoilerTextNode: TextNodeWithEntities?
    private var dustNode: InvisibleInkDustNode?
    private let actionButton: HighlightableButtonNode
    private let actionButtonTitleNode: ImmediateTextNode
    private let actionButtonBackgroundNode: ASImageNode
    
    private let imageNode: TransformImageNode
    private let imageNodeContainer: ASDisplayNode

    private let separatorNode: ASDisplayNode

    private var currentLayout: (CGFloat, CGFloat, CGFloat)?
    private var currentMessage: ChatPinnedMessage?
    private var previousMediaReference: AnyMediaReference?
    private var currentTranslateToLanguage: (fromLang: String, toLang: String)?
    private let translationDisposable = MetaDisposable()
    
    private var isReplyThread: Bool = false
    
    private let fetchDisposable = MetaDisposable()
    
    private var statusDisposable: Disposable?
    private var progressDisposable: Disposable?
    
    private let animationCache: AnimationCache?
    private let animationRenderer: MultiAnimationRenderer?

    private let queue = Queue()
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if let buttonResult = self.buttonsContainer.hitTest(point.offsetBy(dx: -self.buttonsContainer.frame.minX, dy: -self.buttonsContainer.frame.minY), with: event) {
            return buttonResult
        }
        let containerResult = self.contentTextContainer.hitTest(point.offsetBy(dx: -self.contentTextContainer.frame.minX, dy: -self.contentTextContainer.frame.minY), with: event)
        if containerResult?.asyncdisplaykit_node === self.dustNode, self.dustNode?.isRevealed == false {
            return containerResult
        }
        let result = super.hitTest(point, with: event)
        return result
    }
    
    init(context: AccountContext, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) {
        self.context = context
        self.animationCache = animationCache
        self.animationRenderer = animationRenderer
        
        self.tapButton = HighlightTrackingButtonNode()
        
        self.buttonsContainer = ButtonsContainerNode()
        
        self.closeButton = HighlightableButtonNode()
        self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
        self.closeButton.displaysAsynchronously = false
        
        self.actionButton = HighlightableButtonNode()
        self.actionButton.isHidden = true
        self.actionButtonTitleNode = ImmediateTextNode()
        self.actionButtonTitleNode.isHidden = true
        self.actionButtonBackgroundNode = ASImageNode()
        self.actionButtonBackgroundNode.isHidden = true
        self.actionButtonBackgroundNode.displaysAsynchronously = false
        
        self.listButton = HighlightableButtonNode()
        self.listButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0)
        self.listButton.displaysAsynchronously = false
        
        self.activityIndicatorContainer = ASDisplayNode()
        self.activityIndicatorContainer.isUserInteractionEnabled = false
        self.activityIndicator = RadialStatusNode(backgroundNodeColor: .clear)
        self.activityIndicator.isUserInteractionEnabled = false
        self.activityIndicatorContainer.addSubnode(self.activityIndicator)
        self.activityIndicator.alpha = 0.0
        ContainedViewLayoutTransition.immediate.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 0.1)
        
        self.separatorNode = ASDisplayNode()
        self.separatorNode.isLayerBacked = true
        
        self.contextContainer = ContextControllerSourceNode()
        
        self.clippingContainer = ASDisplayNode()
        self.clippingContainer.clipsToBounds = true
        
        self.contentContainer = ASDisplayNode()
        self.contentTextContainer = ASDisplayNode()
        
        self.lineNode = AnimatedNavigationStripeNode()
        
        self.titleNode = AnimatedCountLabelNode()
        self.titleNode.isUserInteractionEnabled = false
        self.titleNode.reverseAnimationDirection = true
        
        self.textNode = TextNodeWithEntities()
        self.textNode.textNode.displaysAsynchronously = false
        self.textNode.textNode.isUserInteractionEnabled = false
        
        self.imageNode = TransformImageNode()
        self.imageNode.contentAnimations = [.subsequentUpdates]
        
        self.imageNodeContainer = ASDisplayNode()
        
        super.init()
        
        self.tapButton.highligthedChanged = { [weak self] highlighted in
            if let strongSelf = self {
                if highlighted {
                    strongSelf.titleNode.layer.removeAnimation(forKey: "opacity")
                    strongSelf.titleNode.alpha = 0.4
                    strongSelf.textNode.textNode.layer.removeAnimation(forKey: "opacity")
                    strongSelf.textNode.textNode.alpha = 0.4
                    strongSelf.lineNode.layer.removeAnimation(forKey: "opacity")
                    strongSelf.lineNode.alpha = 0.4
                } else {
                    strongSelf.titleNode.alpha = 1.0
                    strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                    strongSelf.textNode.textNode.alpha = 1.0
                    strongSelf.textNode.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                    strongSelf.lineNode.alpha = 1.0
                    strongSelf.lineNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                }
            }
        }
        
        self.actionButton.highligthedChanged = { [weak self] highlighted in
            if let strongSelf = self {
                if highlighted {
                    strongSelf.actionButton.layer.removeAnimation(forKey: "opacity")
                    strongSelf.actionButton.alpha = 0.4
                } else {
                    strongSelf.actionButton.alpha = 1.0
                    strongSelf.actionButton.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
                }
            }
        }
        
        self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside])
        self.listButton.addTarget(self, action: #selector(self.listPressed), forControlEvents: [.touchUpInside])
        self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: [.touchUpInside])
        
        self.addSubnode(self.contextContainer)
        
        self.contextContainer.addSubnode(self.clippingContainer)
        self.clippingContainer.addSubnode(self.contentContainer)
        self.contextContainer.addSubnode(self.lineNode)
        self.contentTextContainer.addSubnode(self.titleNode)
        self.contentTextContainer.addSubnode(self.textNode.textNode)
        self.contentContainer.addSubnode(self.contentTextContainer)
        
        self.imageNodeContainer.addSubnode(self.imageNode)
        self.contentContainer.addSubnode(self.imageNodeContainer)
        
        self.actionButton.addSubnode(self.actionButtonBackgroundNode)
        self.actionButton.addSubnode(self.actionButtonTitleNode)
        self.buttonsContainer.addSubnode(self.actionButton)
        self.buttonsContainer.addSubnode(self.closeButton)
        self.buttonsContainer.addSubnode(self.listButton)
        self.contextContainer.addSubnode(self.buttonsContainer)
        self.contextContainer.addSubnode(self.activityIndicatorContainer)
        
        self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside])
        self.contextContainer.addSubnode(self.tapButton)
        
        self.addSubnode(self.separatorNode)
        
        self.contextContainer.activated = { [weak self] gesture, _ in
            guard let strongSelf = self else {
                return
            }
            if let interfaceInteraction = strongSelf.interfaceInteraction, let _ = strongSelf.currentMessage, !strongSelf.isReplyThread {
                interfaceInteraction.activatePinnedListPreview(strongSelf.contextContainer, gesture)
            }
        }
    }
    
    deinit {
        self.fetchDisposable.dispose()
        self.statusDisposable?.dispose()
        self.translationDisposable.dispose()
        self.progressDisposable?.dispose()
    }
    
    private var theme: PresentationTheme?
    
    override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult {
        let panelHeight: CGFloat = 50.0
        var themeUpdated = false
        
        self.contextContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
        
        if self.theme !== interfaceState.theme {
            themeUpdated = true
            self.theme = interfaceState.theme
            self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(interfaceState.theme), for: [])
            self.listButton.setImage(PresentationResourcesChat.chatInputPanelPinnedListIconImage(interfaceState.theme), for: [])
            self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor
            
            self.actionButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 14.0 * 2.0, color: interfaceState.theme.list.itemCheckColors.fillColor, strokeColor: nil, strokeWidth: nil, backgroundColor: nil)
        }
        
        if self.statusDisposable == nil, let interfaceInteraction = self.interfaceInteraction, let statuses = interfaceInteraction.statuses {
            self.statusDisposable = (statuses.loadingMessage
            |> map { status -> Bool in
                return status == .pinnedMessage
            }
            |> deliverOnMainQueue).startStrict(next: { [weak self] isLoading in
                guard let self else {
                    return
                }
                self.updateIsLoading(isLoading: isLoading)
            })
        }
        
        let isReplyThread: Bool
        if case let .replyThread(message) = interfaceState.chatLocation, !message.isForumPost {
            isReplyThread = true
        } else {
            isReplyThread = false
        }
        self.isReplyThread = isReplyThread
        
        self.contextContainer.isGestureEnabled = !isReplyThread
        
        var actionTitle: String?
        var messageUpdated = false
        var messageUpdatedAnimation: PinnedMessageAnimation?
        if let currentMessage = self.currentMessage, let pinnedMessage = interfaceState.pinnedMessage {
            if currentMessage != pinnedMessage {
                messageUpdated = true
            }
            if currentMessage.message.id != pinnedMessage.message.id {
                if currentMessage.message.id < pinnedMessage.message.id {
                    messageUpdatedAnimation = .slideToTop
                } else {
                    messageUpdatedAnimation = .slideToBottom
                }
            }
        } else if (self.currentMessage != nil) != (interfaceState.pinnedMessage != nil) {
            messageUpdated = true
        }
        
        var isStarsPayment = false
        if let message = interfaceState.pinnedMessage, !message.message.isRestricted(platform: "ios", contentSettings: self.context.currentContentSettings.with { $0 }) {
            for attribute in message.message.attributes {
                if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), attribute.rows.count == 1, attribute.rows[0].buttons.count == 1 {
                    let title = attribute.rows[0].buttons[0].title
                    actionTitle = title
                    if case .payment = attribute.rows[0].buttons[0].action, title.contains("⭐️") {
                        isStarsPayment = true
                    }
                }
            }
            
            if actionTitle == nil {
                for media in message.message.media {
                    if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
                        actionTitle = interfaceState.strings.Chat_TitleJoinGroupCall
                    }
                }
            }
        } else {
            actionTitle = nil
        }
        
        var displayCloseButton = false
        var displayListButton = false
        
        if isReplyThread || actionTitle != nil {
            displayCloseButton = false
            displayListButton = false
        } else if let message = interfaceState.pinnedMessage {
            if message.totalCount > 1 {
                displayCloseButton = false
                displayListButton = true
            } else {
                displayCloseButton = true
                displayListButton = false
            }
        } else {
            displayCloseButton = false
            displayListButton = true
        }
        
        if displayCloseButton != !self.closeButton.isHidden {
            if transition.isAnimated {
                if displayCloseButton {
                    self.closeButton.isHidden = false
                    self.closeButton.layer.removeAllAnimations()
                    
                    self.closeButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    self.closeButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
                } else {
                    self.closeButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] completed in
                        guard let strongSelf = self, completed else {
                            return
                        }
                        strongSelf.closeButton.isHidden = true
                        strongSelf.closeButton.layer.removeAllAnimations()
                    })
                    self.closeButton.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
                }
            } else {
                self.closeButton.isHidden = !displayCloseButton
                self.closeButton.layer.removeAllAnimations()
            }
        }
        if displayListButton != !self.listButton.isHidden {
            if transition.isAnimated {
                if displayListButton {
                    self.listButton.isHidden = false
                    self.listButton.layer.removeAllAnimations()
                    
                    self.listButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                    self.listButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
                } else {
                    self.listButton.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self] completed in
                        guard let strongSelf = self, completed else {
                            return
                        }
                        strongSelf.listButton.isHidden = true
                        strongSelf.listButton.layer.removeAllAnimations()
                    })
                    self.listButton.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false)
                }
            } else {
                self.listButton.isHidden = !displayListButton
                self.listButton.layer.removeAllAnimations()
            }
        }
        
        let rightInset: CGFloat = 18.0 + rightInset
        
        var tapButtonRightInset: CGFloat = rightInset
        
        let buttonsContainerSize = CGSize(width: 16.0, height: panelHeight)
        self.buttonsContainer.frame = CGRect(origin: CGPoint(x: width - buttonsContainerSize.width - rightInset, y: 0.0), size: buttonsContainerSize)
        
        let closeButtonSize = self.closeButton.measure(CGSize(width: 100.0, height: 100.0))
        
        if let actionTitle {
            var actionButtonTransition = transition
            var animateButtonIn = false
            if self.actionButton.isHidden {
                actionButtonTransition = .immediate
                animateButtonIn = true
            } else if transition.isAnimated, messageUpdated, actionTitle != self.actionButtonTitleNode.attributedText?.string {
                if let buttonSnapshot = self.actionButton.view.snapshotView(afterScreenUpdates: false) {
                    animateButtonIn = true
                    buttonSnapshot.frame = self.actionButton.frame
                    self.actionButton.view.superview?.insertSubview(buttonSnapshot, belowSubview: self.actionButton.view)
                    
                    buttonSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonSnapshot] _ in
                        buttonSnapshot?.removeFromSuperview()
                    })
                    buttonSnapshot.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
                }
            }
            
            self.actionButton.isHidden = false
            self.actionButtonBackgroundNode.isHidden = false
            self.actionButtonTitleNode.isHidden = false
            
            let attributedTitle: NSAttributedString
            if isStarsPayment {
                let updatedTitle = actionTitle.replacingOccurrences(of: "⭐️", with: " # ")
                let buttonAttributedString = NSMutableAttributedString(string: updatedTitle, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor:  interfaceState.theme.list.itemCheckColors.foregroundColor)
                if let range = buttonAttributedString.string.range(of: "#"), let starImage = UIImage(bundleImageName: "Item List/PremiumIcon") {
                    buttonAttributedString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: buttonAttributedString.string))
                    buttonAttributedString.addAttribute(.foregroundColor, value: interfaceState.theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string))
                    buttonAttributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: buttonAttributedString.string))
                }
                attributedTitle = buttonAttributedString
            } else {
                attributedTitle = NSAttributedString(string: actionTitle, font: Font.with(size: 15.0, design: .round, weight: .semibold, traits: [.monospacedNumbers]), textColor:  interfaceState.theme.list.itemCheckColors.foregroundColor)
            }
            self.actionButtonTitleNode.attributedText = attributedTitle
            
            let actionButtonTitleSize = self.actionButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude))
            let actionButtonSize = CGSize(width: max(actionButtonTitleSize.width + 20.0, 40.0), height: 28.0)
            let actionButtonFrame = CGRect(origin: CGPoint(x: buttonsContainerSize.width + 11.0 - actionButtonSize.width, y: floor((panelHeight - actionButtonSize.height) / 2.0)), size: actionButtonSize)
            actionButtonTransition.updateFrame(node: self.actionButton, frame: actionButtonFrame)
            actionButtonTransition.updateFrame(node: self.actionButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: actionButtonFrame.size))
            actionButtonTransition.updateFrame(node: self.actionButtonTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((actionButtonFrame.width - actionButtonTitleSize.width) / 2.0), y: floorToScreenPixels((actionButtonFrame.height - actionButtonTitleSize.height) / 2.0)), size: actionButtonTitleSize))
            
            tapButtonRightInset = 18.0 + actionButtonFrame.width
            
            if animateButtonIn {
                self.actionButton.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
                self.actionButton.layer.animateScale(from: 0.01, to: 1.0, duration: 0.2)
            }
        } else if !self.actionButton.isHidden {
            self.actionButton.isHidden = true
            self.actionButtonBackgroundNode.isHidden = true
            self.actionButtonTitleNode.isHidden = true
            
            if transition.isAnimated {
                if let buttonSnapshot = self.actionButton.view.snapshotView(afterScreenUpdates: false) {
                    buttonSnapshot.frame = self.actionButton.frame
                    self.actionButton.view.superview?.insertSubview(buttonSnapshot, belowSubview: self.actionButton.view)
                    
                    buttonSnapshot.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonSnapshot] _ in
                        buttonSnapshot?.removeFromSuperview()
                    })
                    buttonSnapshot.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2)
                }
            }
        }
        
        transition.updateFrame(node: self.closeButton, frame: CGRect(origin: CGPoint(x: buttonsContainerSize.width - closeButtonSize.width + 1.0, y: 19.0), size: closeButtonSize))
        
        let listButtonSize = self.listButton.measure(CGSize(width: 100.0, height: 100.0))
        transition.updateFrame(node: self.listButton, frame: CGRect(origin: CGPoint(x: buttonsContainerSize.width - listButtonSize.width + 4.0, y: 13.0), size: listButtonSize))
        
        let indicatorSize = CGSize(width: 22.0, height: 22.0)
        transition.updateFrame(node: self.activityIndicatorContainer, frame: CGRect(origin: CGPoint(x: width - rightInset - indicatorSize.width + 5.0, y: 15.0), size: indicatorSize))
        transition.updateFrame(node: self.activityIndicator, frame: CGRect(origin: CGPoint(), size: indicatorSize))
        
        transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: width, height: UIScreenPixel)))
        self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width - tapButtonRightInset, height: panelHeight))
        
        self.clippingContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
        self.contentContainer.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight))
        
        var translateToLanguage: (fromLang: String, toLang: String)?
        if let translationState = interfaceState.translationState, translationState.isEnabled {
            translateToLanguage = (normalizeTranslationLanguage(translationState.fromLang), normalizeTranslationLanguage(translationState.toLang))
        }
        
        var currentTranslateToLanguageUpdated = false
        if self.currentTranslateToLanguage?.fromLang != translateToLanguage?.fromLang || self.currentTranslateToLanguage?.toLang != translateToLanguage?.toLang {
            self.currentTranslateToLanguage = translateToLanguage
            currentTranslateToLanguageUpdated = true
        }
        
        if currentTranslateToLanguageUpdated || messageUpdated, let message = interfaceState.pinnedMessage?.message {
            if let translation = message.attributes.first(where: { $0 is TranslationMessageAttribute }) as? TranslationMessageAttribute, translation.toLang == translateToLanguage?.toLang {
            } else if let translateToLanguage {
                self.translationDisposable.set(translateMessageIds(context: self.context, messageIds: [message.id], fromLang: translateToLanguage.fromLang, toLang: translateToLanguage.toLang).startStrict())
            }
        }
        
        if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset || messageUpdated || themeUpdated || currentTranslateToLanguageUpdated {
            self.currentLayout = (width, leftInset, rightInset)
            
            let messageUpdated = self.currentMessage?.message.id != interfaceState.pinnedMessage?.message.id
            let previousMessageWasNil = self.currentMessage == nil
            self.currentMessage = interfaceState.pinnedMessage
            
            if let currentMessage = self.currentMessage, let currentLayout = self.currentLayout {
                self.dustNode?.update(revealed: false, animated: false)
                self.enqueueTransition(width: currentLayout.0, panelHeight: panelHeight, leftInset: currentLayout.1, rightInset: currentLayout.2, transition: messageUpdated ? .immediate : transition, animation: messageUpdatedAnimation, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: self.context.account.peerId, firstTime: previousMessageWasNil, isReplyThread: isReplyThread, translateToLanguage: translateToLanguage?.toLang)
            }
        }
        
        self.currentLayout = (width, leftInset, rightInset)
        
        /*if self.currentLayout?.0 != width || self.currentLayout?.1 != leftInset || self.currentLayout?.2 != rightInset || messageUpdated {
            self.currentLayout = (width, leftInset, rightInset)
            
            if let currentMessage = self.currentMessage {
                self.enqueueTransition(width: width, panelHeight: panelHeight, leftInset: leftInset, rightInset: rightInset, transition: .immediate, animation: .none, pinnedMessage: currentMessage, theme: interfaceState.theme, strings: interfaceState.strings, nameDisplayOrder: interfaceState.nameDisplayOrder, dateTimeFormat: interfaceState.dateTimeFormat, accountPeerId: interfaceState.accountPeerId, firstTime: true, isReplyThread: isReplyThread)
            }
        }*/
        
        return LayoutResult(backgroundHeight: panelHeight, insetHeight: panelHeight, hitTestSlop: 0.0)
    }
    
    private func updateIsLoading(isLoading: Bool) {
        let transition: ContainedViewLayoutTransition = .animated(duration: 0.2, curve: .easeInOut)
        if isLoading {
            if self.activityIndicator.alpha.isZero {
                transition.updateAlpha(node: self.activityIndicator, alpha: 1.0)
                transition.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 1.0)
                
                transition.updateAlpha(node: self.buttonsContainer, alpha: 0.0)
                transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 0.1)
                
                if let theme = self.theme {
                    self.activityIndicator.transitionToState(.progress(color: theme.chat.inputPanel.panelControlAccentColor, lineWidth: nil, value: nil, cancelEnabled: false, animateRotation: true), animated: false, completion: {
                    })
                }
            }
        } else {
            if !self.activityIndicator.alpha.isZero {
                transition.updateAlpha(node: self.activityIndicator, alpha: 0.0, completion: { [weak self] completed in
                    if completed {
                        self?.activityIndicator.transitionToState(.none, animated: false, completion: {
                        })
                    }
                })
                transition.updateSublayerTransformScale(node: self.activityIndicatorContainer, scale: 0.1)
                
                transition.updateAlpha(node: self.buttonsContainer, alpha: 1.0)
                transition.updateSublayerTransformScale(node: self.buttonsContainer, scale: 1.0)
            }
        }
    }
    
    private func enqueueTransition(width: CGFloat, panelHeight: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, animation: PinnedMessageAnimation?, pinnedMessage: ChatPinnedMessage, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, accountPeerId: PeerId, firstTime: Bool, isReplyThread: Bool, translateToLanguage: String?) {
        let message = pinnedMessage.message
        
        var animationTransition: ContainedViewLayoutTransition = .immediate
        
        if let animation = animation {
            animationTransition = .animated(duration: 0.2, curve: .easeInOut)
            
            if let copyView = self.textNode.textNode.view.snapshotView(afterScreenUpdates: false) {
                let offset: CGFloat
                switch animation {
                case .slideToTop:
                    offset = -10.0
                case .slideToBottom:
                    offset = 10.0
                }
                
                copyView.frame = self.textNode.textNode.frame
                self.textNode.textNode.view.superview?.addSubview(copyView)
                copyView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: offset), duration: 0.2, removeOnCompletion: false, additive: true)
                copyView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak copyView] _ in
                    copyView?.removeFromSuperview()
                })
                self.textNode.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -offset), to: CGPoint(), duration: 0.2, additive: true)
                self.textNode.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
            }
        } else {
            animationTransition = transition
        }
        
        let makeTitleLayout = self.titleNode.asyncLayout()
        let makeTextLayout = TextNodeWithEntities.asyncLayout(self.textNode)
        let makeSpoilerTextLayout = TextNodeWithEntities.asyncLayout(self.spoilerTextNode)
        let imageNodeLayout = self.imageNode.asyncLayout()
        
        let previousMediaReference = self.previousMediaReference
        let context = self.context
        
        let contentLeftInset: CGFloat = leftInset + 10.0
        var textLineInset: CGFloat = 10.0
        var rightInset: CGFloat = 14.0 + rightInset
        
        let textRightInset: CGFloat = 0.0
        
        if !self.actionButton.isHidden {
            rightInset += self.actionButton.bounds.width - 14.0
        }
        
        var updatedMediaReference: AnyMediaReference?
        var imageDimensions: CGSize?
        
        let giveaway = pinnedMessage.message.media.first(where: { $0 is TelegramMediaGiveaway }) as? TelegramMediaGiveaway
        
        var titleStrings: [AnimatedCountLabelNode.Segment] = []
        if let _ = giveaway {
            titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedGiveaway) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
        } else {
            if pinnedMessage.totalCount == 2 {
                if pinnedMessage.index == 0 {
                    titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedPreviousMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
                } else {
                    titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
                }
            } else if pinnedMessage.totalCount > 1 && pinnedMessage.index != pinnedMessage.totalCount - 1 {
                titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
                titleStrings.append(.text(1, NSAttributedString(string: " #", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
                titleStrings.append(.number(pinnedMessage.index + 1, NSAttributedString(string: "\(pinnedMessage.index + 1)", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
            } else {
                titleStrings.append(.text(0, NSAttributedString(string: "\(strings.Conversation_PinnedMessage) ", font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor)))
            }
        }
        
        if !message.containsSecretMedia {
            for media in message.media {
                if let image = media as? TelegramMediaImage {
                    updatedMediaReference = .message(message: MessageReference(message), media: image)
                    if let representation = largestRepresentationForPhoto(image) {
                        imageDimensions = representation.dimensions.cgSize
                    }
                    break
                } else if let file = media as? TelegramMediaFile {
                    updatedMediaReference = .message(message: MessageReference(message), media: file)
                    if !file.isInstantVideo && !file.isSticker, let representation = largestImageRepresentation(file.previewRepresentations) {
                        imageDimensions = representation.dimensions.cgSize
                    } else if file.isAnimated, let dimensions = file.dimensions {
                        imageDimensions = dimensions.cgSize
                    }
                    break
                } else if let paidContent = media as? TelegramMediaPaidContent, let firstMedia = paidContent.extendedMedia.first {
                    switch firstMedia {
                    case let .preview(dimensions, immediateThumbnailData, _):
                        let thumbnailMedia = TelegramMediaImage(imageId: MediaId(namespace: 0, id: 0), representations: [], immediateThumbnailData: immediateThumbnailData, reference: nil, partialReference: nil, flags: [])
                        if let dimensions {
                            imageDimensions = dimensions.cgSize
                        }
                        updatedMediaReference = .standalone(media: thumbnailMedia)
                    case let .full(fullMedia):
                        updatedMediaReference = .message(message: MessageReference(message), media: fullMedia)
                        if let image = fullMedia as? TelegramMediaImage {
                            if let representation = largestRepresentationForPhoto(image) {
                                imageDimensions = representation.dimensions.cgSize
                            }
                            break
                        } else if let file = fullMedia as? TelegramMediaFile {
                            if let dimensions = file.dimensions {
                                imageDimensions = dimensions.cgSize
                            }
                            break
                        }
                    }
                }
            }
        }
        
        if isReplyThread {
            let titleString: String
            if let author = message.effectiveAuthor {
                titleString = EnginePeer(author).displayTitle(strings: strings, displayOrder: nameDisplayOrder)
            } else {
                titleString = ""
            }
            titleStrings = [.text(0, NSAttributedString(string: titleString, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
        } else {
            for media in message.media {
                if let media = media as? TelegramMediaInvoice {
                    titleStrings = [.text(0, NSAttributedString(string: media.title, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
                    break
                } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
                    titleStrings = [.text(0, NSAttributedString(string: strings.Chat_PinnedGroupCallTitle, font: Font.medium(15.0), textColor: theme.chat.inputPanel.panelControlAccentColor))]
                    break
                }
            }
        }
        
        var applyImage: (() -> Void)?
        if let imageDimensions = imageDimensions {
            let boundingSize = CGSize(width: 35.0, height: 35.0)
            applyImage = imageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: 2.0), imageSize: imageDimensions.aspectFilled(boundingSize), boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets()))
            
            textLineInset += 9.0 + 35.0
        }
        
        var mediaUpdated = false
        if let updatedMediaReference = updatedMediaReference, let previousMediaReference = previousMediaReference {
            mediaUpdated = !updatedMediaReference.media.isEqual(to: previousMediaReference.media)
        } else if (updatedMediaReference != nil) != (previousMediaReference != nil) {
            mediaUpdated = true
        }
        
        let hasSpoiler = message.attributes.contains(where: { $0 is MediaSpoilerMessageAttribute })
        
        var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>?
        var updatedFetchMediaSignal: Signal<FetchResourceSourceType, FetchResourceError>?
        if mediaUpdated {
            if let updatedMediaReference = updatedMediaReference, imageDimensions != nil {
                if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) {
                    if imageReference.media.representations.isEmpty {
                        updateImageSignal = chatSecretPhoto(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, ignoreFullSize: true, synchronousLoad: true)
                    } else {
                        updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), photoReference: imageReference, blurred: hasSpoiler)
                    }
                } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) {
                    if fileReference.media.isAnimatedSticker {
                        let dimensions = fileReference.media.dimensions ?? PixelDimensions(width: 512, height: 512)
                        updateImageSignal = chatMessageAnimatedSticker(postbox: context.account.postbox, userLocation: .peer(message.id.peerId), file: fileReference.media, small: false, size: dimensions.cgSize.aspectFitted(CGSize(width: 160.0, height: 160.0)))
                        updatedFetchMediaSignal = fetchedMediaResource(mediaBox: context.account.postbox.mediaBox, userLocation: .peer(message.id.peerId), userContentType: MediaResourceUserContentType(file: fileReference.media), reference: fileReference.resourceReference(fileReference.media.resource))
                    } else if fileReference.media.isVideo || fileReference.media.isAnimated {
                        updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .peer(message.id.peerId), fileReference: fileReference, blurred: hasSpoiler)
                    } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) {
                        updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .peer(message.id.peerId), mediaReference: fileReference.abstract, representation: iconImageRepresentation)
                    }
                }
            } else {
                updateImageSignal = .single({ _ in return nil })
            }
        }
        let (titleLayout, titleApply) = makeTitleLayout(CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude), .zero, titleStrings)
        
        let (textString, _, isText) = descriptionStringForMessage(contentSettings: context.currentContentSettings.with { $0 }, message: EngineMessage(message), strings: strings, nameDisplayOrder: nameDisplayOrder, dateTimeFormat: dateTimeFormat, accountPeerId: accountPeerId)
        
        let messageText: NSAttributedString
        let textFont = Font.regular(15.0)
        if let giveaway {
            let dateString = stringForDateWithoutYear(date: Date(timeIntervalSince1970: TimeInterval(giveaway.untilDate)), timeZone: .current, strings: strings)
            let currentTime = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970)
            let isFinished = currentTime >= giveaway.untilDate
            let text: String
            if isFinished {
                let winnersString = strings.Conversation_PinnedGiveaway_Finished_Winners(giveaway.quantity)
                text = strings.Conversation_PinnedGiveaway_Finished(winnersString, dateString).string
            } else {
                let winnersString = strings.Conversation_PinnedGiveaway_Ongoing_Winners(giveaway.quantity)
                text = strings.Conversation_PinnedGiveaway_Ongoing(winnersString, dateString).string
            }
            messageText = NSAttributedString(string: text, font: textFont, textColor: theme.chat.inputPanel.primaryTextColor)
        } else if isText {
            var text = message.text
            var messageEntities = message.textEntitiesAttribute?.entities ?? []
            
            if let translateToLanguage = translateToLanguage, !text.isEmpty {
                for attribute in message.attributes {
                    if let attribute = attribute as? TranslationMessageAttribute, !attribute.text.isEmpty, attribute.toLang == translateToLanguage {
                        text = attribute.text
                        messageEntities = attribute.entities
                        break
                    }
                }
            }
            
            let entities = messageEntities.filter { entity in
                switch entity.type {
                case .Spoiler, .CustomEmoji:
                    return true
                default:
                    return false
                }
            }
            let textColor = theme.chat.inputPanel.primaryTextColor
            if entities.count > 0 {
                messageText = stringWithAppliedEntities(trimToLineCount(text, lineCount: 1), entities: entities, baseColor: textColor, linkColor: textColor, baseFont: textFont, linkFont: textFont, boldFont: textFont, italicFont: textFont, boldItalicFont: textFont, fixedFont: textFont, blockQuoteFont: textFont, underlineLinks: false, message: message)
            } else {
                messageText = NSAttributedString(string: foldLineBreaks(text), font: textFont, textColor: textColor)
            }
        } else {
            messageText = NSAttributedString(string: foldLineBreaks(textString.string), font: textFont, textColor: message.media.isEmpty || message.media.first is TelegramMediaWebpage ? theme.chat.inputPanel.primaryTextColor : theme.chat.inputPanel.secondaryTextColor)
        }
        
        let textConstrainedSize = CGSize(width: width - textLineInset - contentLeftInset - rightInset - textRightInset, height: CGFloat.greatestFiniteMagnitude)
        let (textLayout, textApply) = makeTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0)))
        
        let spoilerTextLayoutAndApply: (TextNodeLayout, (TextNodeWithEntities.Arguments?) -> TextNodeWithEntities)?
        if !textLayout.spoilers.isEmpty {
            spoilerTextLayoutAndApply = makeSpoilerTextLayout(TextNodeLayoutArguments(attributedString: messageText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets(top: 2.0, left: 0.0, bottom: 2.0, right: 0.0), displaySpoilers: true, displayEmbeddedItemsUnderSpoilers: true))
        } else {
            spoilerTextLayoutAndApply = nil
        }
        
        let strongSelf = self
        let _ = titleApply(animation != nil)
        
        var textArguments: TextNodeWithEntities.Arguments?
        if let cache = strongSelf.animationCache, let renderer = strongSelf.animationRenderer {
            textArguments = TextNodeWithEntities.Arguments(
                context: strongSelf.context,
                cache: cache,
                renderer: renderer,
                placeholderColor: theme.list.mediaPlaceholderColor,
                attemptSynchronous: false
            )
        }
        let _ = textApply(textArguments)
        
        strongSelf.previousMediaReference = updatedMediaReference
        
        animationTransition.updateFrameAdditive(node: strongSelf.contentTextContainer, frame: CGRect(origin: CGPoint(x: contentLeftInset + textLineInset, y: 0.0), size: CGSize(width: width, height: panelHeight)))
        
        strongSelf.titleNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 5.0), size: titleLayout.size)
        
        let textFrame = CGRect(origin: CGPoint(x: 0.0, y: 23.0), size: textLayout.size)
        strongSelf.textNode.textNode.frame = textFrame
        
        if let (_, spoilerTextApply) = spoilerTextLayoutAndApply {
            let spoilerTextNode = spoilerTextApply(textArguments)
            if strongSelf.spoilerTextNode == nil {
                spoilerTextNode.textNode.alpha = 0.0
                spoilerTextNode.textNode.isUserInteractionEnabled = false
                spoilerTextNode.textNode.contentMode = .topLeft
                spoilerTextNode.textNode.contentsScale = UIScreenScale
                spoilerTextNode.textNode.displaysAsynchronously = false
                strongSelf.contentTextContainer.insertSubnode(spoilerTextNode.textNode, aboveSubnode: strongSelf.textNode.textNode)
                
                strongSelf.spoilerTextNode = spoilerTextNode
            }
            
            strongSelf.spoilerTextNode?.textNode.frame = textFrame
            
            let dustNode: InvisibleInkDustNode
            if let current = strongSelf.dustNode {
                dustNode = current
            } else {
                dustNode = InvisibleInkDustNode(textNode: spoilerTextNode.textNode, enableAnimations: strongSelf.context.sharedContext.energyUsageSettings.fullTranslucency)
                strongSelf.dustNode = dustNode
                strongSelf.contentTextContainer.insertSubnode(dustNode, aboveSubnode: spoilerTextNode.textNode)
            }
            dustNode.frame = textFrame.insetBy(dx: -3.0, dy: -3.0).offsetBy(dx: 0.0, dy: 3.0)
            dustNode.update(size: dustNode.frame.size, color: theme.chat.inputPanel.secondaryTextColor, textColor: theme.chat.inputPanel.primaryTextColor, rects: textLayout.spoilers.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) }, wordRects: textLayout.spoilerWords.map { $0.1.offsetBy(dx: 3.0, dy: 3.0).insetBy(dx: 1.0, dy: 1.0) })
        } else if let spoilerTextNode = strongSelf.spoilerTextNode {
            strongSelf.spoilerTextNode = nil
            spoilerTextNode.textNode.removeFromSupernode()
            
            if let dustNode = strongSelf.dustNode {
                strongSelf.dustNode = nil
                dustNode.removeFromSupernode()
            }
        }
        
        strongSelf.textNode.visibilityRect = CGRect.infinite
        strongSelf.spoilerTextNode?.visibilityRect = CGRect.infinite
        
        let lineFrame = CGRect(origin: CGPoint(x: contentLeftInset, y: 0.0), size: CGSize(width: 2.0, height: panelHeight))
        animationTransition.updateFrame(node: strongSelf.lineNode, frame: lineFrame)
        strongSelf.lineNode.update(
            colors: AnimatedNavigationStripeNode.Colors(
                foreground: theme.chat.inputPanel.panelControlAccentColor,
                background: theme.chat.inputPanel.panelControlAccentColor.withAlphaComponent(0.5),
                clearBackground: theme.chat.inputPanel.panelBackgroundColor
            ),
            configuration: AnimatedNavigationStripeNode.Configuration(
                height: panelHeight,
                index: pinnedMessage.index,
                count: pinnedMessage.totalCount
            ),
            transition: animationTransition
        )
        
        strongSelf.imageNodeContainer.frame = CGRect(origin: CGPoint(x: contentLeftInset + 9.0, y: 7.0), size: CGSize(width: 35.0, height: 35.0))
        strongSelf.imageNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 35.0, height: 35.0))
        
        if let applyImage = applyImage {
            applyImage()
            
            animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 1.0)
            animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 1.0, beginWithCurrentState: true)
        } else {
            animationTransition.updateSublayerTransformScale(node: strongSelf.imageNodeContainer, scale: 0.1)
            animationTransition.updateAlpha(node: strongSelf.imageNodeContainer, alpha: 0.0, beginWithCurrentState: true)
        }
        
        if let updateImageSignal = updateImageSignal {
            strongSelf.imageNode.setSignal(updateImageSignal)
        }
        if let updatedFetchMediaSignal = updatedFetchMediaSignal {
            strongSelf.fetchDisposable.set(updatedFetchMediaSignal.startStrict())
        }
    }
    
    @objc func tapped() {
        if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
            if self.isReplyThread {
                interfaceInteraction.scrollToTop()
            } else {
                interfaceInteraction.navigateToMessage(message.message.id, false, true, .pinnedMessage)
            }
        }
    }
    
    @objc func closePressed() {
        if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
            interfaceInteraction.unpinMessage(message.message.id, true, nil)
        }
    }
    
    @objc func listPressed() {
        if let interfaceInteraction = self.interfaceInteraction, let message = self.currentMessage {
            interfaceInteraction.openPinnedList(message.message.id)
        }
    }
    
    @objc private func actionButtonPressed() {
        if let interfaceInteraction = self.interfaceInteraction, let controller = interfaceInteraction.chatController() as? ChatControllerImpl, let controllerInteraction = controller.controllerInteraction, let message = self.currentMessage?.message {
            for attribute in message.attributes {
                if let attribute = attribute as? ReplyMarkupMessageAttribute, attribute.flags.contains(.inline), attribute.rows.count == 1, attribute.rows[0].buttons.count == 1 {
                    let button = attribute.rows[0].buttons[0]
                    switch button.action {
                    case .text:
                        controllerInteraction.sendMessage(button.title)
                        return
                    case let .url(url):
                        var isConcealed = true
                        if url.hasPrefix("tg://") {
                            isConcealed = false
                        }
                        controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: isConcealed, progress: Promise()))
                        return
                    case .requestMap:
                        controllerInteraction.shareCurrentLocation()
                        return
                    case .requestPhone:
                        controllerInteraction.shareAccountContact()
                        return
                    case .openWebApp:
                        let progressPromise = Promise<Bool>()
                        controllerInteraction.requestMessageActionCallback(message, nil, true, false, progressPromise)
                        self.progressDisposable?.dispose()
                        self.progressDisposable = (progressPromise.get()
                        |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                            guard let self else {
                                return
                            }
                            self.updateIsLoading(isLoading: value)
                        })
                        
                        return
                    case let .callback(requiresPassword, data):
                        let progressPromise = Promise<Bool>()
                        controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword, progressPromise)
                        self.progressDisposable?.dispose()
                        self.progressDisposable = (progressPromise.get()
                        |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                            guard let self else {
                                return
                            }
                            self.updateIsLoading(isLoading: value)
                        })
                        return
                    case let .switchInline(samePeer, query, peerTypes):
                        var botPeer: Peer?
                        
                        var found = false
                        for attribute in message.attributes {
                            if let attribute = attribute as? InlineBotMessageAttribute {
                                if let peerId = attribute.peerId {
                                    botPeer = message.peers[peerId]
                                    found = true
                                }
                            }
                        }
                        if !found {
                            botPeer = message.author
                        }
                        
                        var peerId: PeerId?
                        if samePeer {
                            peerId = message.id.peerId
                        }
                        if let botPeer = botPeer, let addressName = botPeer.addressName {
                            controllerInteraction.activateSwitchInline(peerId, "@\(addressName) \(query)", peerTypes)
                        }
                        return
                    case .payment:
                        controllerInteraction.openCheckoutOrReceipt(message.id, nil)
                        return
                    case let .urlAuth(url, buttonId):
                        controllerInteraction.requestMessageActionUrlAuth(url, .message(id: message.id, buttonId: buttonId))
                        return
                    case .setupPoll:
                        break
                    case let .openUserProfile(peerId):
                        let _ = (self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
                        |> deliverOnMainQueue).startStandalone(next: { peer in
                            if let peer = peer {
                                controllerInteraction.openPeer(peer, .info(nil), nil, .default)
                            }
                        })
                        return
                    case let .openWebView(url, simple):
                        controllerInteraction.openWebView(button.title, url, simple, .generic)
                        return
                    case .requestPeer:
                        break
                    case let .copyText(payload):
                        controllerInteraction.copyText(payload)
                        return
                    }
                    
                    break
                }
            }
            
            for media in message.media {
                if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content, content.type == "telegram_call" {
                    var isConcealed = true
                    if content.url.hasPrefix("tg://") {
                        isConcealed = false
                    }
                    let progressPromise = Promise<Bool>()
                    controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(
                        url: content.url,
                        concealed: isConcealed,
                        message: message,
                        progress: progressPromise
                    ))
                    
                    self.progressDisposable?.dispose()
                    self.progressDisposable = (progressPromise.get()
                    |> deliverOnMainQueue).startStrict(next: { [weak self] value in
                        guard let self else {
                            return
                        }
                        self.updateIsLoading(isLoading: value)
                    })
                    
                    return
                }
            }
        }
    }
}
